/**
 * @author: zhangxinxu(.com)
 * @see: https://www.zhangxinxu.com/wordpress/2017/05/html-accesskey/
 * @description: 快捷键提示
 * HTML accesskey辅助增强脚本
 * 作用包括：
 * 1. 统一IE浏览器和其它浏览器的快捷访问行为；
 * 2. 增加单独accesskey属性值对应按键按下的focus行为；
 * 3. 增加accesskey生效的前置键按下的提示行为，例如window上Alt键提示，Mac下的Alt + control组合提示等；
 * 4. 增加shift + '?'(keyCode=191)键的提示行为支持；
 * MIT
 */
(function (doc) {
  // 获取用户代理信息
  const ua = navigator.userAgent;
  // 默认系统为windows
  let system = "windows";
  // 判断是否为Mac系统
  if (/Mac\sOS\sX/i.test(ua)) {
    system = "mac";
  }
  // 默认浏览器为chrome
  let browser = "chrome";
  // 判断浏览器类型
  if (typeof doc.mozFullSoreen != void 0) {
    browser = "moz";
  } else if (typeof doc.msHidden != void 0 || typeof doc.hidden == void 0) {
    browser = "ie";
  }
  // 定义快捷键前缀
  const keyPrefix = {
    windows:
      browser == "moz"
        ? {
            ctrlKey: false,
            altKey: true,
            shiftKey: true,
          }
        : {
            ctrlKey: false,
            altKey: true,
            shiftKey: false,
          },
    mac: {
      ctrlKey: true,
      altKey: true,
      shiftKey: false,
    },
  }[system];

  // 定义一个函数U，接收两个参数a和b
  var U = function (a, b) {
    // 如果a为空，则返回空字符串
    if (!a) return "";
    // 如果b为空，则默认为"x"
    for (var b = b || "x", c = "", d = 0, e; d < a.length; d += 1)
      // 如果a的字符编码在55296到56319之间，则进行特殊处理
      a.charCodeAt(d) >= 55296 && a.charCodeAt(d) <= 56319
        ? ((e = (
            65536 +
            1024 * (Number(a.charCodeAt(d)) - 55296) +
            Number(a.charCodeAt(d + 1)) -
            56320
          ).toString(16)),
          (d += 1))
        : (e = a.charCodeAt(d).toString(16)),
        // 将处理后的字符编码添加到c中
        (c += b + e);
    // 返回c去掉b后的字符串
    return c.substr(b.length);
  };

  // 定义一个定时器变量
  let timerTips = null;

  // 定义一个移除提示函数
  const removeTips = function () {
    clearTimeout(timerTips);
    // 移除所有的快捷键提示
    var elesTips = doc.querySelectorAll(".AK_Tips");
    [].slice.call(elesTips).forEach(function (ele) {
      if (ele.fromTarget) {
        ele.fromTarget.hasTipsShow = null;
      }
      doc.body.removeChild(ele);
    });
    doc.hasTipsShow = null;
  };

  // 定义一个提示函数
  const tips = function (arrEles) {
    // 已经显示中，忽略
    if (doc.hasTipsShow) {
      return;
    }
    // 页面的滚动高度
    var scrollTop = doc.documentElement.scrollTop || doc.body.scrollTop;
    var scrollLeft = doc.documentElement.scrollLeft || doc.body.scrollLeft;

    // 遍历创建提示元素
    arrEles.forEach(function (ele) {
      // 如果元素隐藏，也忽略
      if (ele.clientHeight * ele.clientWidth == 0) {
        return;
      }

      var accesskey = ele.getAttribute("accesskey");
      var arrAccesskey = [];
      for (var key in keyPrefix) {
        if (keyPrefix[key]) {
          arrAccesskey.push(key);
        }
      }
      arrAccesskey.push(accesskey);

      // 当前元素相对于文档的偏移
      var bounding = ele.getBoundingClientRect();

      // 创建tips提示元素
      var div = document.createElement("div");
      div.className = "AK_Tips";
      div.setAttribute(
        "style",
        "position:absolute;top:" +
          (bounding.top + scrollTop) +
          "px;left:" +
          (bounding.left + scrollLeft) +
          'px;z-index:9999;font-family:consolas,"Liberation Mono",courier,monospace;font-size:12px;border-radius:2px;color:#fff;background:rgba(0,0,0,.75);opacity:0.8;line-height:13px;padding:0 3px;'
      );
      div.innerHTML = arrAccesskey
        .map(function (key) {
          return (
            '<kbd style="font-family:inherit;">' +
            key.replace("Key", "") +
            "</kbd>"
          );
        })
        .join("+");

      document.body.appendChild(div);

      div.fromTarget = ele;
    });

    // 标记，避免重复生成
    doc.hasTipsShow = true;

    // 一段时间隐藏
    timerTips = setTimeout(function () {
      removeTips();
    }, 3000);
  };

  // 判断是否支持addEventListener方法
  if (doc.addEventListener) {
    // IE9+
    doc.addEventListener("keydown", function (event) {
      // 当前元素是否是可输入的input或者textarea
      var isTargetInputable = false;
      var eleTarget = event.target || doc.activeElement;
      var tagName = eleTarget.tagName.toLowerCase();
      if (
        tagName == "textarea" ||
        (tagName == "input" &&
          /checkbox|radio|select|file|button|image|hidden/.test(
            eleTarget.type
          ) == false)
      ) {
        isTargetInputable = true;
      }

      // 遍历所有设置了accesskey的符合HTML4.0.1规范的元素
      // 包括<a>, <area>, <button>, <input>, <label>, <legend>以及<textarea>
      var elesOwnAccesskey = doc.querySelectorAll(
        "a[accesskey],area[accesskey],button[accesskey],input[accesskey],label[accesskey],legend[accesskey],textarea[accesskey]"
      );
      if (elesOwnAccesskey.length == 0) {
        return;
      }
      // 对象列表转换成数组
      var arrElesOwnAccesskey = [].slice.call(elesOwnAccesskey);
      // 进行遍历
      var arrAcceeekey = arrElesOwnAccesskey.map(function (ele) {
        return ele.getAttribute("accesskey");
      });
      // windows下图下直接event.key就是按下的键对于的内容，但OS X系统却没有key属性，有的是event.keyIdentifier，表示字符的Unicode值
      // 根据这个判断按键是否有对应的匹配
      var indexMatch = -1;
      arrAcceeekey.forEach(function (accesskey, index) {
        if (
          (event.key && event.key == accesskey) ||
          (event.keyIdentifier &&
            parseInt(event.keyIdentifier.toLowerCase().replace("u+", ""), 16) ==
              parseInt(U(accesskey), 16))
        ) {
          indexMatch = index;
          return false;
        }
      });

      // 1. 单独按下某个键的匹配支持
      if (
        event.altKey == false &&
        event.shiftKey == false &&
        event.ctrlKey == false
      ) {
        if (isTargetInputable) {
          return;
        }
        // focus高亮
        if (arrElesOwnAccesskey[indexMatch]) {
          arrElesOwnAccesskey[indexMatch].focus();
          // 阻止内容输入
          event.preventDefault();
        }
        // 2. shift + '?'(keyCode=191)键的提示行为支持
      } else if (
        event.altKey == false &&
        event.shiftKey == true &&
        event.ctrlKey == false
      ) {
        if (event.keyCode == 191 && !isTargetInputable) {
          doc.hasTipsShow ? removeTips() : tips(arrElesOwnAccesskey);
        }
        // 3. 增加accesskey生效的前置键按下的提示行为
      } else if (
        event.altKey == keyPrefix.altKey &&
        event.shiftKey == keyPrefix.shiftKey &&
        event.ctrlKey == keyPrefix.ctrlKey
      ) {
        if (indexMatch == -1) {
          event.preventDefault();
          doc.hasTipsShow ? removeTips() : tips(arrElesOwnAccesskey);
        } else {
          removeTips();
        }

        // 4. IE浏览器和其他浏览器行为一致的处理
        if (
          browser == "ie" &&
          arrElesOwnAccesskey[indexMatch] &&
          !isTargetInputable
        ) {
          // click行为触发
          arrElesOwnAccesskey[indexMatch].click();
        }
      }
    });

    doc.addEventListener("mousedown", removeTips);
  }
})(document);
